// implements game logic; code is shared between client and server

#include "logic.h"
#include "action.h"
#include "map.h"
#include "game.h"
#include "network.h"
#include "map.h"
#include "resources.h"
#include "bailout.h"

int game_time = 0; // in ticks passed since game start
void (*damage_handler) (int time, coord_t where, int attacker, int victim, int weapon, int damage) = NULL;

static int is_in_bounds (Map* map, int x, int y);
static int tile_is_obstructed (Map* map, int x, int y, int z);
static void execute_action (Action* action);
static void action_begin (Action* action, Map* map);
static void action_finished (Action* action);
int action_is_valid (Map* map, const Action* action);
static Action get_force_followup_action (Map* map, const Action* action);
static Action get_default_followup_action (const Action* action);

void logic_reset ()
{
	game_time = 0;
	damage_handler = NULL;
	for (int i = 0; i < MAX_PLAYERS; i++) {
		action_free (&player[i].action);
		action_free (&player[i].want_action);
		player[i].action = no_action;
		player[i].want_action = no_action;
	}
}

// Returns the time when this actor can do another action
int time_asap (Player* actor)
{
	return game_time + actor->action.cooldown;
}

void logic_update (Map* map)
{
	for (int i = 0; i < MAX_PLAYERS; i++) {
		Player* p = &player[i];
		if (p->data.nickname[0] == '\0') continue; // noone here
		Action* a = &p->action;
		Action* wa = &p->want_action;

		execute_action (a);
		
		while ((a->cooldown <= 0) || ((wa->type != ACTION_NONE) && (wa->start_time <= game_time))) {
			action_finished (a);
			Action next_action = get_force_followup_action (map, a);
			if (next_action.type == ACTION_NONE) {
				if (p->want_action.type == ACTION_NONE) {
					next_action = get_default_followup_action (a);
				} else {
					next_action = *wa;
				}
			}
				
			if (!action_is_valid (map, &next_action)) {
				Action trash = next_action;
				if (next_action.type == ACTION_MOVE) {
					next_action = action_alloc_halt(next_action.actor, time_asap(p));
				} else {
					next_action = no_action;
				}
				action_free (&trash);
			}
				
			action_free (a);
			*a = next_action;
			*wa = get_default_followup_action (a);
			action_begin (a, map);
			
			if (a->type == ACTION_NONE) break;
		}
	}
	
	game_time++;
}

void logic_enqueue (const Action* action)
{
	assert (actor_type (action->actor) == ACTOR_PLAYER); // Monsters to be implemented
	
	Player* p = (Player*) player_from_actor_id (action->actor);
	p->want_action = *action;
}

static int is_in_bounds (Map* map, int x, int y)
{
	return (x >= 0) && (x < map->width) && (y >= 0) && (y < map->height);
}

// obstructions handled until now:
// - walls
// - other players
static int tile_is_obstructed (Map* map, int x, int y, int z)
{
	if (map_tile(map,x,y,z)->type == TILE_WALL) return 1;
	
	for (int i = 0; i < MAX_PLAYERS; i++) {
		if (*player[i].data.nickname == 0) continue;
		if ((player[i].pos.x == x) && (player[i].pos.y == y) && (player[i].pos.z == z)) return 1;
	}
	
	return 0;
}

static void execute_action (Action* action)
{
	Player* p;
	int cooldown_passed;
	
	switch (action->type) {
	
	case ACTION_NONE:
	case ACTION_TURN:
	case ACTION_HALT:
	case ACTION_RESPAWN:
		break;
		
	case ACTION_MOVE:
		if (game_time % 4 == 0) {
			assert (actor_type (action->actor) == ACTOR_PLAYER); // TODO: implement monsters
			p = (Player*) player_from_actor_id (action->actor);
			p->anim.frame++;
		}
		break;
		
	case ACTION_DIE:
		if (game_time % (FPS/10) == 0) {
			assert (actor_type (action->actor) == ACTOR_PLAYER); // TODO: implement monsters
			p = (Player*) player_from_actor_id (action->actor);
			p->anim.frame++;
		}
		break;
	
	case ACTION_TRAVERSE:
		break;
		
	case ACTION_ATTACK_CLOSE:
		cooldown_passed = items[action->cattack->weapon].cooldown - action->cooldown; // TODO: this cooldown system will change
		if ((cooldown_passed % (FPS/10)) == (FPS/10-1)) {
			p = (Player*) player_from_actor_id (action->actor);
			p->anim.frame++;
		}
		break;
	
	case ACTION_ATTACK_RANGE:
		#if TOWERHACK_DEBUG
		fprintf (stderr, "logic: execute ranged attack with weapon %d\n", action->rattack->weapon);
		#endif
		cooldown_passed = items[action->rattack->weapon].cooldown - action->cooldown; // TODO: this cooldown system will change
		if ((cooldown_passed % (FPS/10)) == (FPS/10-1)) {
			p = (Player*) player_from_actor_id (action->actor);
			p->anim.frame++;
		}
		break;
	
	default:
		#if TOWERHACK_DEBUG
		fprintf (stderr, "logic: cannot execute action of type %d, ignored!\n", action->type);
		#else
		assert (0);
		#endif
		break;
		
	}
	
	action->cooldown--;
}

static int calc_damage (int attacker, int victim, InvSlot weapon)
{
	Player* a = (Player*) player_from_actor_id (attacker);
	Player* v = (Player*) player_from_actor_id (victim);

	int phys_d = items[a->inv.slot[weapon]].physical_damage;
	int mag_d  = items[a->inv.slot[weapon]].magic_damage;
	int imm_d  = items[a->inv.slot[weapon]].immaterial_damage;

	double phys_r = items[v->inv.slot[INV_ARMOR]].physical_reduce;
	double mag_r  = items[v->inv.slot[INV_ARMOR]].magic_reduce;
	double imm_r  = items[v->inv.slot[INV_ARMOR]].immaterial_reduce;

	return phys_r * phys_d + mag_r * mag_d + imm_r * imm_d;
}

static void action_begin (Action* action, Map* map)
{
	/* #if TOWERHACK_DEBUG 
	if (action->type != ACTION_NONE) {
		fprintf (stderr, "action_begin: current game_time is %d\n", game_time);
		fprintf (stderr, "action_begin: player %d action %d time %d cooldown %d\n", action->actor, action->type, action->start_time, action->cooldown);
	}
	#endif */
		
	if (action->type == ACTION_NONE) return;
		
	assert (actor_type (action->actor) == ACTOR_PLAYER);
	Player* p = (Player*) player_from_actor_id (action->actor);
	
	// Updating the pos & dir on every action helps with network sync
	coord_t c = action->location;
	p->pos.x = c.x;
	p->pos.y = c.y;
	p->pos.z = c.z;
	p->pos.dir = action->dir;
		
	switch (action->type) {
	
	case ACTION_NONE:
	case ACTION_TURN:
	case ACTION_HALT:
		break;
		
	case ACTION_MOVE:
		c = direction_apply (c, action->dir, 1);
		p->pos.x = c.x;
		p->pos.y = c.y;
		p->pos.z = c.z;
		break;
	
	case ACTION_TRAVERSE:
		break;
	
	case ACTION_ATTACK_CLOSE:
		p->anim.frame = 0;
		coord_t from = { p->pos.x, p->pos.y, p->pos.z };
		coord_t to = direction_apply (from, p->pos.dir, 1);
		
		map_effect (map, effect_make_weapon (resources_get_weapon (action->cattack->weapon), p->pos.dir, from));
		
		for (int i = 0; i < MAX_PLAYERS; i++) 
		if (*player[i].data.nickname != '\0') {
			coord_t c = { player[i].pos.x, player[i].pos.y, player[i].pos.z };
			if ((c.x == to.x) && (c.y == to.y) && (c.z == to.z)) {			
				int attacker = action->actor;	
				int victim = actor_id_from_player ((struct Player*) &player[i]);
				int damage = calc_damage (attacker, victim, INV_WEAPON_CLOSE);
				
				if (damage_handler != NULL)
					damage_handler (game_time + (FPS/10), to, attacker, victim, action->cattack->weapon, damage);
			}
		}
		break;

	case ACTION_ATTACK_RANGE:
		p->anim.frame = 0;
		// TODO: deal damage
		break;

	case ACTION_DIE:
		p->anim.frame = 0;
		break;

	case ACTION_RESPAWN:
		p->pos.x = p->checkpoint.x;
		p->pos.y = p->checkpoint.y;
		p->pos.z = p->checkpoint.z;
		p->hp = p->skill.max_hp;
		break;

	default:
		#if TOWERHACK_DEBUG
		fprintf (stderr, "logic: cannot begin action of type %d, ignored!\n", action->type);
		#else
		assert (0);
		#endif
		break;

	}
}

static void action_finished (Action* action)
{
	/* #if TOWERHACK_DEBUG
	if (action->type != ACTION_NONE) {
		fprintf (stderr, "action_finished: current game_time is %d\n", game_time);
		fprintf (stderr, "action_finished: player %d action %d time %d cooldown %d\n", action->actor, action->type, action->start_time, action->cooldown);
	}
	#endif */

	if (action->type == ACTION_NONE) return;

	assert (actor_type (action->actor) == ACTOR_PLAYER);
	Player* p = (Player*) player_from_actor_id (action->actor);

	switch (action->type) {

	case ACTION_NONE:
	case ACTION_TURN:
	case ACTION_MOVE:
	case ACTION_HALT:
	case ACTION_RESPAWN:
		break;

	case ACTION_TRAVERSE:
		p->pos.x = action->traverse->target.x;
		p->pos.y = action->traverse->target.y;
		p->pos.z = action->traverse->target.z;
		p->want_action = no_action; // any moves we had in mind are now invalid
		break;

	case ACTION_ATTACK_CLOSE:
	case ACTION_ATTACK_RANGE:
	case ACTION_DIE:
		p->anim.frame = 0;
		break;

	default:
		#if TOWERHACK_DEBUG
		fprintf (stderr, "logic: cannot end action of type %d, ignored!\n", action->type);
		#else
		assert (0);
		#endif
		break;

	}
}

int action_is_valid (Map* map, const Action* action)
{
	if (action->type == ACTION_NONE) return 1;

	assert (actor_type (action->actor) == ACTOR_PLAYER); // Monsters to be implemented
	Player* p = (Player*) player_from_actor_id (action->actor);
	coord_t c;

	switch (action->type) {

	case ACTION_TURN:
		// Outlawing a turn when it's unnecessary saves network traffic.
		if  (p->pos.dir != action->dir) break;
		
	case ACTION_MOVE:
		c = direction_apply (action->location, action->dir, 1);
		if (!is_in_bounds(map, c.x, c.y) || tile_is_obstructed (map, c.x, c.y, c.z)) return 0;
		break;
	
	case ACTION_TRAVERSE:
		// TODO: Check if "from" and "to" tiles match (either stairs or teleporters)
		break;
		
	default:
		break;
		
	}
	
	return 1;
}

static Action get_force_followup_action (Map* map, const Action* action)
{
	if (action->type == ACTION_MOVE) {
		coord_t to = direction_apply (action->location, action->dir, 1);
		coord_t c = to;
		int index = map_index(map, c.x, c.y, c.z);
		Tile* t = &map->tile[index];
		switch (t->type) {
			case TILE_EXIT_UP:
				c.z++;
				while (c.z < map->num_layers) {
					index = map_index(map, c.x, c.y, c.z);
					if (map->tile[index].type == TILE_EXIT_DOWN) {
						Action a = action_alloc_traverse(action->actor, game_time, c);
						return a;
					}
					c.z++;
				}
				c = to;
				fprintf(stderr, "No matching EXIT_DOWN for EXIT_UP at (%d,%d,%d) found!\n", c.x, c.y, c.z);
				// assert(0);     // Error policy for game logic: "best effort", try to ignore errors
				return no_action; // Simply do not traverse the EXIT

			case TILE_EXIT_DOWN:
				c.z--;
				while (c.z >= 0) {
					index = map_index(map, c.x, c.y, c.z);
					if (map->tile[index].type == TILE_EXIT_UP) {
						Action a = action_alloc_traverse(action->actor, game_time, c);
						return a;
					}
					c.z--;
				}
				c = to;
				fprintf(stderr, "No matching EXIT_UP for EXIT_DOWN at (%d,%d,%d) found!\n", c.x, c.y, c.z);
				// assert(0);     // Error policy for game logic: "best effort", try to ignore errors
				return no_action; // Simply do not traverse the EXIT

			case TILE_TELEPORTER:
				return action_alloc_traverse(action->actor, game_time, t->teleporter.target);

			default:
				break;
		}
	} else
	if (action->type == ACTION_TURN) {
		return action_alloc_move (action->actor, game_time, action->dir);
	} else
	if (action->type == ACTION_DIE) {
		return action_alloc_respawn (action->actor, game_time);
	}
	
	return no_action;
}

static Action get_default_followup_action (const Action* action)
{
	if (action->type == ACTION_MOVE) {
		return action_alloc_move (action->actor, action->start_time + COOLDOWN_MOVE, action->dir);
	}
	
	return no_action;
}

